/*
 * @Descripttion: unTitle
 * @Author: yizheng.yuan
 * @Date: 2021-11-01 11:01:21
 * @LastEditors: yizheng.yuan
 * @LastEditTime: 2022-01-02 15:27:44
 */

// 一定要仔细看下面的文字：

/**
 * 2022.12.11 我用测试号
 * 1.修改appId，appsecret，token
 * 2.用natapp内网穿透80端口得到地址
 * 3.接口配置信息修改
 * 4.JS接口安全域名修改
 * 5.扫码关注了，测试号二维码
 * 6.体验接口权限表：网页授权获取用户基本信息修改：域名ayuzek.natappfree.cc
 * 
 * 7.正式账号：网页授权获取用户基本信息 获得条件：
 * 个人订阅号无法开通此接口
 * 公司营业执照 服务号必须通过微信认证
 */

// 使用过程中有任何问题，记得加我微信号 yizheng369问我
// 这里面会出现各种问题的。
// 在启动项目之前，要运行 npm i 
// 接着要运行 npm i request
// 最后启动后台：方法1：，运行 node app.js
// 最后启动后台：方法2：，运行 nodemon app.js（如果你还没安装nodemon就要先运行这句 npm i nodemon -g ）

// 问题点1：ngrok必须要通过--authtoken参数登录，才能通过微信token验证，否则不行
// （你必须要拥有一个ngrok账号，或者用github账号登录也行,没有的就去注册）
// 这样运行 ngrok.exe http 8090 --authtoken 1V46bLRGCfv5GK0WhTVmfNgXFaX_37oyzuEBkUSzbCKKcL2RQ
// 注意：这里建议用natapp进行内网穿透代替ngrok，因为ngrok经常出现异常
// natapp的使用视频：https://www.bilibili.com/video/BV19T4y1U7yG/?spm_id_from=333.337.search-card.all.click&vd_source=125d808bbbad2b8400f221b816a0f674


// 问题点2：-跨域设置会导致返回的页面为字符串string，故不能这样设置：res.header("Content-Type", "application/json;charset=utf-8");

// 问题3：jsapi如果不完整的话，记得要【扫码关注测试公众号】，微信测试号页面找到这句话

// 提醒：这个是视频讲解过程，遇到问题是，请认真看视频 https://www.bilibili.com/video/BV1XL411T73G/
// 详细完整视频 微信公众号开发接收信息 https://m.bilibili.com/video/BV1XJ411P7T4?p=10&share_medium=iphone&share_plat=ios&share_source=WEIXIN&share_tag=s_i&timestamp=1648654864&unique_k=U06F2iS

const fs = require("fs");
const express = require('express');
var bodyParser = require('body-parser');
var sha1 = require('sha1');
const request = require('request')
const path = require('path');
const app = express();
const port = 80;
const staticUrl = 'html'

// 解析 application/json
// app.use(bodyParser.json());
// // 解析 application/x-www-form-urlencoded
// app.use(bodyParser.urlencoded());
// 接收参数
// app.use(express.json())
// app.use(express.urlencoded({extended: true}))

// 参考文章：https://cloud.tencent.com/developer/article/1679242
// 在Node.JS的app.js或者server.js中，在bodyparser中修改这个限制即可：
app.use(bodyParser.json({limit:'100mb'}));
app.use(bodyParser.urlencoded({ limit:'100mb', extended: true }));

// 返回对象
let gRel = {
  error: 0,
  msg: "ok",
  data: ""
}
let ngRel = {
  error: 1,
  msg: "fail",
  data: ""
}

// 工具类 
const { getXMLStr ,getJsData, getObjData} = require('./utils/tool')

// 服务器地址
const serverUrl = 'http://95ybqq.natappfree.cc';

// 获取个人信息回调地址 
const userInfo_redirect_url = `${serverUrl}/login.html`;

// 测试号配置：记得你们要改成你们自己的公众号上面的
const wxConfig = {
  appId: 'wx3680aee5b396cc0f',                    // 这个要改成你自己的
  appsecret: '626c988a2e4af8cc459424187951792e',  // 这个也要改成你自己的
  token: '123456', // 这个是你自己随便写的，写成123abc也行
  serverUrl: serverUrl,
  userInfo_redirect_url: userInfo_redirect_url
}


// 有效期
var enableTimestamp = 0;

// 过渡时间 提早5分钟，重新获取token
var transitionTime = 5 * 60 * 1000;

// 处理跨域
//设置跨域访问 
app.all('*', function (req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
  res.header("X-Powered-By", ' 3.2.1')
  next();
})

// 处理静态资源访问
app.use(`/${staticUrl}`, express.static(staticUrl)); 

/**
 * 上传图片
 */
 app.post('/wx_upload_img', async (req, res) => {
  // 参考 https://www.cnblogs.com/lxz123/p/15093004.html
  console.log('数据code1:', req.body.openid);
  let data = req.body
  // 将数据保存到json文件里
  let arr = require('./db.json')
  let hasOne = false;
  for(let i=0;i<arr.length;i++){
    if(arr[i].openid == data.openid){
      arr[i] = Object.assign(arr[i], data);
      hasOne = true;
      break;
    }
  }
  if(!hasOne){
    arr.push(data)
  }
  // 将数据写回到文件里
  fs.writeFileSync("./db.json", JSON.stringify(arr, null, 2))
  let backRel = Object.assign({}, gRel)
  backRel.msg = "上传图片成功"
  res.send(backRel)
})

/**
 * 获取个人详细数据
 */
 app.post('/getMyData', async (req, res) => {
  // 参考 https://www.cnblogs.com/lxz123/p/15093004.html
  console.log('数据code1:', req.body.openid);
  let data = req.body
  // 将数据保存到json文件里
  let arr = require('./db.json')
  let hasOne = false;
  for(let i=0;i<arr.length;i++){
    if(arr[i].openid == data.openid){
      hasOne = arr[i];
      break;
    }
  }
  let backRel = Object.assign({}, gRel)
  backRel.data = hasOne
  res.send(backRel)
})

/**
 * 获取配置信息
 */
 app.get("/getConfig", (req,res)=>{
  console.log('进来：',Date.now());
  res.send(wxConfig)
})


// 获取用户信息，三部曲
// 感谢网友的文章 https://blog.csdn.net/qq_39506978/article/details/109410343
// 参考微信官方文档：https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#3
app.get("/login.html", (req, res) => {
  console.log(Date.now()+':login.html页面响应--用户，有信息 ：', req.body, req.query);
  // 如果是获取用户信息的回调，就重定向 
  if(req.query && req.query.code){
    let code = req.query.code
    res.redirect(`/${staticUrl}?code=`+code)
  }
})

// 通过后台，获取用户信息1 java 前端拉起授权页面，用户授权，拿到code，再次请求用户信息
app.get("/getUserInfo", (req,res)=>{
  let code = req.query.code
  // 通过code，获取用户信息
  let url = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${wxConfig.appId}&secret=${wxConfig.appsecret}&code=${code}&grant_type=authorization_code`
    
  request(url, async function (error, response, body) {
    if (!error) {
      console.log('openId_成功_用户信息：error, response, body', typeof body)
      
      // 最后获取用户信息
      let userInfo = await getUserInfo(JSON.parse(body))
      console.log('userInfo',userInfo);
      res.send(userInfo)
    } else {
      console.log('error：', error)
      res.send(JSON.parse(error))
    }
  });
})

// 1.获取token 令牌 通过postman获取即可 与视频不同
// get  
// let url =`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&
// appid=wx3680aee5b396cc0f&secret=13d16b772027b3d257b8e1ba28df9652`
// URL需要正确响应微信发送的Token验证 http://www.xcaipu.cn/weixin
// JS接口安全域名 www.xcaipu.cn/wx_js
// access_token: 令牌（有效期7200秒，开发者必须在自己的服务全局缓存access_token）
var access_token_obj = {
  "access_token": "51_wy_LaL72FJc9HI1cnlRnm6kYtpjVho7tzZl4IDhBzLWsxSsRBB3tQ79KGp1f9kuoBO6JY33Ay3F4wjI94LLoVWQNsyrmLeuvuN2IkIHGxpusIfbOxcCkHwOiA5bRJubyBJj3f8KVpunx0Rl4ZUMgAJAKFX",
  "expires_in": 7200
}


// 2.获取ticket：门票 通过postman获取即可 与视频不同
// ticket：门票（有效期7200秒，开发者必须在自己的服务全局缓存jsapi_ticket）
// https://api.weixin.qq.com/cgi-bin/ticket/getticket?
// access_token=51_wy_LaL72FJc9HI1cnlRnm6kYtpjVho7tzZl4IDhBzLWsxSsRBB3tQ79KGp1f9kuoBO6JY33Ay3F4wjI94LLoVWQNsyrmLeuvuN2IkIHGxpusIfbOxcCkHwOiA5bRJubyBJj3f8KVpunx0Rl4ZUMgAJAKFX&type=jsapi
var ticketObj = {
  "errcode": 0,
  "errmsg": "ok",
  "ticket": "O3SMpm8bG7kJnF36aXbe84asaV6bl616leMYTutwmpXjnTbzL73w1zJTV5QYP0yMBkUG-I4jskS9mYbomoF4Hg",
  "expires_in": 7200
}

/**
 * 获取微信公众号二维码--扫码登录
 * 2022-03-27
 * 
 */
 app.post('/getQrCode', async (req, res) => {
  // 参考 https://www.cnblogs.com/lxz123/p/15093004.html
  console.log('数据code1:', req.body);
  let data = req.body
  let ticketObj = await getQr_ticket(data)
  res.send(ticketObj)
})

/**
 * 获取用户信息时，需要配置的微信回调地址，在授权表格右侧配置
 */
app.post('/wxback', async (req, res) => {
  // 参考 https://www.cnblogs.com/lxz123/p/15093004.html
  console.log('获取用户信息时-微信回调:', req.body);
  
  res.send("ticketObj")
})

function getQr_ticket(data) {
  var url = `https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=${access_token_obj.access_token}`
  return new Promise((resolve, reject) => {
    request.post(
      {
        url,
        json: data
      },
      async function (error, response, body) {
        if (!error) {
          resolve(body)
        } else {
          reject(JSON.parse(error))
        }
      }
    );
  })
}

// 方法写在下面
app.post('/code', (req, res) => {
  console.log('数据code1:', req.body);
  const { code } = req.body;
  var url = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${wxConfig.appId}&secret=${wxConfig.appsecret}&code=${code}&grant_type=authorization_code`
  return new Promise((resolve, reject) => {
    request(url, async function (error, response, body) {
      if (!error) {
        console.log('openId_成功11：error, response, body', typeof body, body)
        // resolve(JSON.parse(body))\
        let userInfo = await getUserInfo(JSON.parse(body))
        res.send(userInfo)

      } else {
        console.log('error：', error)
        // reject(error)
        res.send(JSON.parse(error))
      }
    });
  })
})

function getUserInfo(obj) {
  return new Promise((resolve, reject) => {
    let url = `https://api.weixin.qq.com/sns/userinfo?access_token=${obj.access_token}&openid=${obj.openid}&lang=zh_CN`
    request(url, function (error, response, body) {
      if (!error) {
        console.log('getUserInfo_成功:, response, body', typeof body, body)
        // resolve(JSON.parse(body))\
        resolve(JSON.parse(body))
      } else {
        console.log('error：', error)
        // reject(error)
        reject(error)
      }
    });
  })
}

// 注意这个是post请求，作为试验的 2022.12.11 回顾
app.post('/getUserInfo', (req, res) => {
  console.log('数据code :', req.body);
  const { code } = req.body
  return new Promise((resolve, reject) => {
    // 这个是别人的服务器地址
    const server = 'http://qiaolianyun.viphk.91tunnel.com/wxsss/OpenIDss'
    // const server= 'http://qiaolianyun.viphk.91tunnel.com/servlet/getUserInfo';
    const other_server = `${server}?code=${code}`
    request(other_server, function (error, response, body) {
      if (!error) {
        console.log('getRight_成功11：error, response, body', typeof body, body)
        // resolve(JSON.parse(body))\
        res.send(JSON.parse(body))
      } else {
        console.log('error：', error)
        // reject(error)
        res.send(JSON.parse(error))
      }
    });
  })
})

// 获取openId 
// scope为snsapi_base
var redirect_uri = `${serverUrl}/${staticUrl}/`
var snsapi_base_Url = `https://open.weixin.qq.com/connect/oauth2/authorize?
          appid=${wxConfig.appId}
          &redirect_uri=${encodeURIComponent(redirect_uri)}
          &response_type=code
          &scope=snsapi_base
          &state=123#wechat_redirect`;

function getRight(right_url) {
  return new Promise((resolve, reject) => {
    request(right_url, function (error, response, body) {
      if (!error) {
        console.log('getRight_成功11：error, response, body', typeof body, body)
        resolve(true)
      } else {
        console.log('error：', error)
        reject(error)
      }
    });
  })
}

app.get('/getRight', async (req, res) => {
  let rel = await getRight(snsapi_base_Url);
  console.log('object');
  res.send(rel)
})

// 正式服务器验证文件
app.get('/MP_verify_4PEd2oiCAfH93OeQ.txt',async (req,res)=>{
  res.sendFile(path.resolve(__dirname,'./MP_verify_4PEd2oiCAfH93OeQ.txt'))
})

// 获取新的token 
function getNewToken() {
  return new Promise((resolve, reject) => {
    let token_url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${wxConfig.appId}&secret=${wxConfig.appsecret}`;

    request(token_url, function (error, response, body) {
      if (!error) {
        console.log('成功1：error, response, body', typeof body, body)
        resolve(JSON.parse(body))
      } else {
        console.log('error：', error)
        reject(error)
      }
    });

  })
}

// 获取新的ticket
function getNewTicket(access_token) {
  console.log('access_token', access_token);
  return new Promise((resolve, reject) => {
    let ticket_url = `https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${access_token}&type=jsapi`;
    request(ticket_url, function (error, response, body) {
      if (!error) {
        console.log('成功2：error, response, body', body)
        resolve(JSON.parse(body))
      } else {
        console.log('error：', error)
        reject(error)
      }
    });

  })
}

// 报错判断
// 63002: 应该是该ip不在白名单内 或者 参与加密的url跟公众号上面的测试号jsapi域名不一致导致的 请检查

// 获取微信api需要的配置参数 
app.get('/wx', async (req, res) => {

  // 现在
  const nowTimeStamp = Date.now();

  // 1.判断当前的有效期，是否有效
  if ((enableTimestamp - transitionTime) < nowTimeStamp) {
    console.log('过期了：重新请求---');
    // 如果是在过渡时间内了，要重新请求
    let rel = await getNewToken()

    // 根据获取到的token，继续获取ticket
    console.log('rel:', rel);
    access_token_obj = rel;
    ticketObj = await getNewTicket(rel.access_token);
    enableTimestamp += nowTimeStamp + ticketObj.expires_in * 1000;
    console.log('ticketObj:', ticketObj);
  } else {
    console.log('未过期--使用旧的1');
  }

  const obj2 = {
    noncestr: (Math.random() + '').split('.')[1],
    jsapi_ticket: ticketObj.ticket,
    timestamp: nowTimeStamp,
    url: `${serverUrl}/html/`
  }
  console.log("obj2",obj2)

  let js_arr = [
    `jsapi_ticket=${obj2.jsapi_ticket}`,
    `noncestr=${obj2.noncestr}`,
    `timestamp=${obj2.timestamp}`,
    `url=${obj2.url}`
  ];

  let js_str = js_arr.sort().join('&')
  console.log('js_str', js_str);

  let signature = sha1(js_str);
  console.log('signature', signature);
  const config_obj = {
    appId: wxConfig.appId, // 必填，公众号的唯一标识 
    timestamp: nowTimeStamp, // 必填，生成签名的时间戳
    nonceStr: obj2.noncestr, // 必填，生成签名的随机串
    signature,// 必填，签名 
  }
  res.send(config_obj)
})


// app.get('/getAddress',(req,res)=>{
//   console.log('req',req.body,req.query);
//   let {url} = req.query;

// request(url, function (error, response, body) {
//   if(!error){
//     console.log('error, response, body', body)//打印百度首页html内容
//     res.send({
//       error: 0,
//       data: JSON.parse(body)
//     })
//   }else{
//     console.log('error',error)
//     res.send({
//       error:1,
//       data: error
//     })
//   }

// })
//   // rq.get({url, resolveWithFullResponse:true})
//   // .then(res=>{
//   //   console.log('res',res);
//   // })
//   // .then(err=>{
//   //   console.log('err',err);
//   // })

// })

// 接收信息
// app.get('/msg', (req, res) => {
//   console.log(Date.now()+':根路径，有信息：', req.body, req.query);
//   res.send(true)
// })




// 配合微信验证token配置方法：
app.get('/', (req, res) => {
  console.log('get请求:根路径，有信息 ：', req.body, req.query);
  // 解构参数
  let { signature, echostr, timestamp, nonce } = req.query;
  let relStr = getValidateStr(req)
  // 然后和signature比较，是否一致

  if (relStr == signature) {
    console.log('验证通过-2--');
    // res.send(true)
    res.send(echostr)
  } else {
    console.log('验证不通过-1--');
    res.send(false)
  }
})

// 验证信息是否来自微信服务器
function getValidateStr(req) {
  let { token } = wxConfig;
  console.log('我的token:', token);
  let { signature, echostr, timestamp, nonce } = req.query;
  // 将 token, timestamp, nonce 三项按照字典排序
  let arr = [token, timestamp, nonce];
  arr = arr.sort();
  console.log('sort-arr', arr);
  let arrStr = arr.join('');
  // 然后通过sha1加密
  const relStr = sha1(arrStr);
  console.log('relStr', relStr);
  return relStr;
}


// 接收微信发来的消息 我傻了 居然想不到用post方法
app.post('/', async (req, res) => {
  console.log('post-home:', req.query);
  let { signature, echostr, timestamp, nonce } = req.query;
  let relStr = getValidateStr(req)
  if (relStr == signature) {
    console.log('信息来自微信服务器--');

    // 提取信息
    let xmlData = await getXMLStr(req);
    console.log('xmlData:', xmlData);
    /** 微信服务器返回了的xml格式数据
    <xml>
      <ToUserName><![CDATA[gh_b3958963bb18]]></ToUserName>
      <FromUserName><![CDATA[od4SM6Y8InFQGTfBjsiMRhkteIAE]]></FromUserName>
      <CreateTime>1648658404</CreateTime>
      <MsgType><![CDATA[text]]></MsgType>
      <Content><![CDATA[3]]></Content>
      <MsgId>23603117248352202</MsgId>
    </xml>
     */

    // 通过工具解析xml数据
    let jsData = await getJsData(xmlData)
    console.log('jsData:',jsData);

    // 再次优化数据
    let msgObj = getObjData(jsData.xml)
    console.log('msgObj:',msgObj);

    // 回复信息给 微信服务器
    let content = ''
    if(msgObj.MsgType == 'text'){
      if(msgObj.Content == 1){
        content = '很快就成功了'
      } else if(msgObj.Content == 2){
        content = '再坚持一会，就成功了'
      } else if(msgObj.Content.includes('爱')){
        content = '爱你一万年！'
      } else {
        content = '你可以发送: 1，2，3，爱你！'
      }
    }
    else if(msgObj.MsgType == 'event'){
      content = 'event事件'
      if(msgObj.Event == 'SCAN'){
        content = '好家伙，手机扫码'
      } else if(msgObj.Event == 'subscribe'){
        content = '好家伙，欢迎您的关注！'
      }
      if(msgObj.Event == 'unsubscribe'){
        content = '好家伙，你居然敢取笑关注？'
      }
    }
    else{
      content = '其他信息来源！'
    }
    

    // 根据来时的信息格式，重组返回。(注意中间不能有空格)
    let msgStr = `<xml>
      <ToUserName><![CDATA[${msgObj.FromUserName}]]></ToUserName>
      <FromUserName><![CDATA[${msgObj.ToUserName}]]></FromUserName>
      <CreateTime>${Date.now()}</CreateTime>
      <MsgType><![CDATA[text]]></MsgType>
      <Content><![CDATA[${content}]]></Content>
    </xml>`
    res.send(msgStr)
    // 非常感谢尚硅谷的视频
    // 微信公众号开发接收信息https://m.bilibili.com/video/BV1XJ411P7T4?p=10&share_medium=iphone&share_plat=ios&share_source=WEIXIN&share_tag=s_i&timestamp=1648654864&unique_k=U06F2iS

  } else {
    console.log('信息来历不明--');
  }
})



app.listen(port, () => {
  console.log('server open at: http://localhost:' + port);
})